Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ Download

feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt renovate/actions-setup-java-5.x (f17e586c) Text, 17.45 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.connections

Tff7b72import T7ee787androidx.lifecycle.ViewModel
Tff7b72import T7ee787androidx.lifecycle.viewModelScope
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.combine
Tff7b72import T7ee787kotlinx.coroutines.flow.distinctUntilChanged
Tff7b72import T7ee787kotlinx.coroutines.flow.flatMapLatest
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOn
Tff7b72import T7ee787kotlinx.coroutines.flow.launchIn
Tff7b72import T7ee787kotlinx.coroutines.flow.map
Tff7b72import T7ee787kotlinx.coroutines.flow.onEach
Tff7b72import T7ee787kotlinx.coroutines.flow.update
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleScanner
Tff7b72import T7ee787org.meshtastic.core.ble.MeshtasticBleConstants
Tff7b72import T7ee787org.meshtastic.core.datastore.RecentAddressesDataSource
Tff7b72import T7ee787org.meshtastic.core.datastore.model.RecentAddress
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.core.model.RadioController
Tff7b72import T7ee787org.meshtastic.core.model.util.anonymize
Tff7b72import T7ee787org.meshtastic.core.network.repository.NetworkRepository
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioPrefs
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.UiPrefs
Tff7b72import T7ee787org.meshtastic.core.ui.viewmodel.safeLaunch
Tff7b72import T7ee787org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
Tff7b72import T7ee787org.meshtastic.feature.connections.model.DeviceListEntry
Tff7b72import T7ee787org.meshtastic.feature.connections.model.DiscoveredDevices
Tff7b72import T7ee787org.meshtastic.feature.connections.model.GetDiscoveredDevicesUseCase
Tff7b72import T7ee787kotlin.time.Duration

T8b949e/**
* Platform-neutral ViewModel that drives the Connections screen: device discovery (BLE/USB/TCP), scan state, current
* selection, and connection-progress chatter.
*
* Subclassed per-platform (see `AndroidScannerViewModel`, `JvmScannerViewModel`) to plug in platform-specific bonding /
* permission flows.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tff7b72open Tff7b72class T56d364ScannerViewModelTb4b4b4(
Tff7b72protected Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioControllerTb4b4b4: Te6edf3RadioControllerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceServiceTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioPrefsTb4b4b4: Te6edf3RadioPrefsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3recentAddressesDataSourceTb4b4b4: Te6edf3RecentAddressesDataSourceTb4b4b4,
Tff7b72private Tff7b72val Te6edf3getDiscoveredDevicesUseCaseTb4b4b4: Te6edf3GetDiscoveredDevicesUseCaseTb4b4b4,
Tff7b72private Tff7b72val Te6edf3networkRepositoryTb4b4b4: Te6edf3NetworkRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tff7b72private Tff7b72val Te6edf3uiPrefsTb4b4b4: Te6edf3UiPrefsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3bleScannerTb4b4b4: Te6edf3BleScanner? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3ViewModelTb4b4b4(Tb4b4b4) Tb4b4b4{

T8b949e// ── Mock / demo transport ─────────────────────────────────────────────────────────────────
Tff7b72private Tff7b72val Te6edf3_showMockTransport Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3showMockTransportTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3_showMockTransportTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

T8b949e// ── Connection-progress chatter (surfaced as the bottom status pill) ──────────────────────
Tff7b72private Tff7b72val Te6edf3_connectionProgressText Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657String?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)

T8b949e/**
* Transient, fine-grained status text emitted during connect/bonding (e.g. "Bonding…", "Requesting config…").
* Nullable because `serviceRepository.connectionProgress` does not emit during steady-state.
*
* Persistent "Not connected / Connecting / Connected" copy is derived separately in
* `ConnectionsViewModel.connectionStatus` so the UI can choose `progress ?: status`.
*/
Tff7b72val Te6edf3connectionProgressTextTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657String?Tff7b72> Tff7b72= Te6edf3_connectionProgressTextTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

T8b949e// ── BLE scanning ──────────────────────────────────────────────────────────────────────────
Tff7b72private Tff7b72val Te6edf3_isBleScanning Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3isBleScanningTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3_isBleScanningTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

T8b949e/** User preference that controls whether BLE scanning auto-starts when the Connections screen opens. */
Tff7b72val Te6edf3bleAutoScanTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3bleAutoScan

Tff7b72private Tff7b72val Te6edf3scannedBleDevices Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3MapTff7b72<Tffa657StringTb4b4b4, Te6edf3BleDeviceTff7b72>Tff7b72>Tb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3discoveryOrder Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3ListTff7b72<Tffa657StringTff7b72>Tff7b72>Tb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72var Te6edf3scanJobTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null

T8b949e// ── Network scanning (NSD gating) ─────────────────────────────────────────────────────────
Tff7b72private Tff7b72val Te6edf3_isNetworkScanning Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72val Te6edf3isNetworkScanningTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3_isNetworkScanningTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

T8b949e/** User preference that controls whether NSD network scanning auto-starts when the Connections screen opens. */
Tff7b72val Te6edf3networkAutoScanTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3networkAutoScan

T8b949e// ── Transport-section visibility (filter chips) ───────────────────────────────────────────

T8b949e/** Whether the BLE section is visible in the Connections device list. Defaults to `true`. */
Tff7b72val Te6edf3showBleTransportTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3showBleTransport

T8b949e/** Whether the Network (TCP/NSD) section is visible in the Connections device list. Defaults to `true`. */
Tff7b72val Te6edf3showNetworkTransportTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3showNetworkTransport

T8b949e/** Whether the USB section is visible in the Connections device list. Defaults to `true`. */
Tff7b72val Te6edf3showUsbTransportTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3showUsbTransport

Tff7b72fun Td2a8ffsetShowBleTransportTb4b4b4(Te6edf3enabledTb4b4b4: Tffa657BooleanTb4b4b4) Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3setShowBleTransportTb4b4b4(Te6edf3enabledTb4b4b4)

Tff7b72fun Td2a8ffsetShowNetworkTransportTb4b4b4(Te6edf3enabledTb4b4b4: Tffa657BooleanTb4b4b4) Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3setShowNetworkTransportTb4b4b4(Te6edf3enabledTb4b4b4)

Tff7b72fun Td2a8ffsetShowUsbTransportTb4b4b4(Te6edf3enabledTb4b4b4: Tffa657BooleanTb4b4b4) Tff7b72= Te6edf3uiPrefsTb4b4b4.Te6edf3setShowUsbTransportTb4b4b4(Te6edf3enabledTb4b4b4)

T8b949e/**
* Resolved NSD services flow, gated by [_isNetworkScanning]. When scanning is inactive, emits `emptyList()` so
* `NsdManager.discoverServices()` is never triggered. Android 15+ shows a system consent dialog the first time
* `resolvedList` is subscribed, so the gate ensures NSD only runs when the user explicitly requests it.
*/
Tff7b72private Tff7b72val Te6edf3gatedResolvedList Tff7b72=
Te6edf3_isNetworkScanningTb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Te6edf3scanning Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3scanningTb4b4b4) Te6edf3networkRepositoryTb4b4b4.Te6edf3resolvedList Tff7b72else Te6edf3flowOfTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72val Te6edf3discoveredDevicesFlowTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3DiscoveredDevicesTff7b72> Tff7b72=
Te6edf3showMockTransport
Tb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Te6edf3showMock Tff7b72-Tff7b72> Te6edf3getDiscoveredDevicesUseCaseTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3showMockTb4b4b4, Te6edf3gatedResolvedListTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3DiscoveredDevicesTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72init Tb4b4b4{
Te6edf3_showMockTransportTb4b4b4.Te6edf3value Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3isMockTransportTb4b4b4(Tb4b4b4)
Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionProgressTb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3_connectionProgressTextTb4b4b4.Te6edf3value Tff7b72= Tffa657it Tb4b4b4}Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3viewModelScopeTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffScannerViewModel createdTa5d6ff" Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffonClearedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72superTb4b4b4.Te6edf3onClearedTb4b4b4(Tb4b4b4)
Te6edf3stopBleScanTb4b4b4(Tb4b4b4)
Te6edf3stopNetworkScanTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffScannerViewModel clearedTa5d6ff" Tb4b4b4}
Tb4b4b4}

T8b949e// ── Device lists for UI ──────────────────────────────────────────────────────────────────

T8b949e/**
* Combined bonded + scanned BLE devices for the UI.
*
* Sorted for stability to prevent "shifting" as advertisements arrive: bonded devices always appear first (sorted
* by name), followed by unbonded scanned devices in the order they were first discovered. RSSI updates are
* reflected on the cards but do not trigger a re-sort.
*/
Tff7b72val Te6edf3bleDevicesForUiTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3DeviceListEntryTff7b72>Tff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3discoveredDevicesFlowTb4b4b4, Te6edf3scannedBleDevicesTb4b4b4, Te6edf3discoveryOrderTb4b4b4) Tb4b4b4{ Te6edf3discoveredTb4b4b4, Te6edf3scannedMapTb4b4b4, Te6edf3order Tff7b72-Tff7b72>
Tff7b72val Te6edf3bonded Tff7b72= Te6edf3discoveredTb4b4b4.Te6edf3bleDevicesTb4b4b4.Te6edf3filterIsInstanceTff7b72<Te6edf3DeviceListEntryTb4b4b4.Te6edf3BleTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3bondedAddresses Tff7b72= Te6edf3bondedTb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3address Tb4b4b4}

T8b949e// Scanned-but-not-bonded devices are explicitly flagged unbonded so the UI routes through
T8b949e// requestBonding() β€” which on Android triggers createBond() for the pairing dialog before connecting.
T8b949e// Preserves discovery order to prevent items jumping around during the scan burst.
Tff7b72val Te6edf3unbondedScanned Tff7b72=
Te6edf3order
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657it Tff7b72!in Te6edf3bondedAddresses Tb4b4b4}
Tb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Te6edf3address Tff7b72-Tff7b72>
Te6edf3scannedMapTff7b72[Te6edf3addressTff7b72]Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3DeviceListEntryTb4b4b4.Te6edf3BleTb4b4b4(Te6edf3device Tff7b72= Tffa657itTb4b4b4, Te6edf3bonded Tff7b72= Tff7b72falseTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// For bonded devices, attach the latest scan RSSI (if we've seen an advertisement this session) so the
T8b949e// UI can show the signal indicator, but keep them sorted by name for stability.
Tff7b72val Te6edf3bondedForUi Tff7b72=
Te6edf3bonded
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3entry Tff7b72-Tff7b72>
Tff7b72val Te6edf3scanned Tff7b72= Te6edf3scannedMapTff7b72[Te6edf3entryTb4b4b4.Te6edf3addressTff7b72]
Tff7b72if Tb4b4b4(Te6edf3scanned Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3scannedTb4b4b4.Te6edf3rssi Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Te6edf3entryTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3device Tff7b72= Te6edf3scannedTb4b4b4) Tff7b72else Te6edf3entry
Tb4b4b4}
Tb4b4b4.Te6edf3sortedBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3name Tb4b4b4}

Te6edf3bondedForUi Tff7b72+ Te6edf3unbondedScanned
Tb4b4b4}
Tb4b4b4.Te6edf3flowOnTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4)
Tb4b4b4.Te6edf3distinctUntilChangedTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3usbDevicesForUiTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3DeviceListEntryTff7b72>Tff7b72> Tff7b72=
Te6edf3discoveredDevicesFlowTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3usbDevices Tb4b4b4}Tb4b4b4.Te6edf3distinctUntilChangedTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3discoveredTcpDevicesForUiTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3DeviceListEntryTff7b72>Tff7b72> Tff7b72=
Te6edf3discoveredDevicesFlowTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3discoveredTcpDevices Tb4b4b4}Tb4b4b4.Te6edf3distinctUntilChangedTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3recentTcpDevicesForUiTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3DeviceListEntryTff7b72>Tff7b72> Tff7b72=
Te6edf3discoveredDevicesFlowTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3recentTcpDevices Tb4b4b4}Tb4b4b4.Te6edf3distinctUntilChangedTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e// ── Current selection ────────────────────────────────────────────────────────────────────

Tff7b72val Te6edf3selectedAddressFlowTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657String?Tff7b72> Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3currentDeviceAddressFlow

T8b949e/** The persisted device name from the last selection, for use as a UI fallback. */
Tff7b72val Te6edf3persistedDeviceNameTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657String?Tff7b72> Tff7b72= Te6edf3radioPrefsTb4b4b4.Te6edf3devName

T8b949e/** Non-null variant of [selectedAddressFlow] that substitutes [NO_DEVICE_SELECTED] for `null`. */
Tff7b72val Te6edf3selectedNotNullFlowTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657StringTff7b72> Tff7b72=
Te6edf3selectedAddressFlow
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657it Tff7b72?: Te6edf3NO_DEVICE_SELECTED Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3selectedAddressFlowTb4b4b4.Te6edf3value Tff7b72?: Te6edf3NO_DEVICE_SELECTEDTb4b4b4)

T8b949e// ── Scan commands ────────────────────────────────────────────────────────────────────────

Tff7b72fun Td2a8ffstartBleScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_isBleScanningTb4b4b4.Te6edf3value Tff7b72|Tff7b72| Te6edf3bleScanner Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return

Te6edf3_isBleScanningTb4b4b4.Te6edf3value Tff7b72= Tff7b72true

Te6edf3scanJob Tff7b72=
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffstartBleScanTa5d6ff"Tb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3bleScanner
Tb4b4b4.Te6edf3scanTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3DurationTb4b4b4.Te6edf3INFINITETb4b4b4, Te6edf3serviceUuid Tff7b72= Te6edf3MeshtasticBleConstantsTb4b4b4.Te6edf3SERVICE_UUIDTb4b4b4)
Tb4b4b4.Te6edf3flowOnTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4)
Tb4b4b4.Te6edf3collect Tb4b4b4{ Te6edf3device Tff7b72-Tff7b72>
Te6edf3scannedBleDevicesTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3current Tff7b72-Tff7b72>
Tff7b72val Te6edf3existing Tff7b72= Te6edf3currentTff7b72[Te6edf3deviceTb4b4b4.Te6edf3addressTff7b72]
T8b949e// Replace if RSSI changed so the UI reflects the latest advertisement. Keep the same
T8b949e// instance otherwise to avoid unnecessary recomposition.
Tff7b72if Tb4b4b4(Te6edf3existing Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3existingTb4b4b4.Te6edf3rssi Tff7b72=Tff7b72= Te6edf3deviceTb4b4b4.Te6edf3rssiTb4b4b4) Tb4b4b4{
Te6edf3current
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3current Tff7b72+ Tb4b4b4(Te6edf3deviceTb4b4b4.Te6edf3address Te6edf3to Te6edf3deviceTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3deviceTb4b4b4.Te6edf3address Tff7b72!in Te6edf3discoveryOrderTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
Te6edf3discoveryOrderTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657it Tff7b72+ Te6edf3deviceTb4b4b4.Te6edf3address Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3_isBleScanningTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72fun Td2a8ffstopBleScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3scanJobTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3scanJob Tff7b72= Tff7b72null
Te6edf3_isBleScanningTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tb4b4b4}

T8b949e/** Convenience command: start scanning if idle, stop otherwise. Persists the resulting state to prefs. */
Tff7b72fun Td2a8fftoggleBleScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_isBleScanningTb4b4b4.Te6edf3valueTb4b4b4) Te6edf3stopBleScanTb4b4b4(Tb4b4b4) Tff7b72else Te6edf3startBleScanTb4b4b4(Tb4b4b4)
Te6edf3uiPrefsTb4b4b4.Te6edf3setBleAutoScanTb4b4b4(Te6edf3_isBleScanningTb4b4b4.Te6edf3valueTb4b4b4)
Tb4b4b4}

Tff7b72fun Td2a8ffstartNetworkScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3_isNetworkScanningTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tb4b4b4}

Tff7b72fun Td2a8ffstopNetworkScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3_isNetworkScanningTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tb4b4b4}

T8b949e/** Convenience command: start scanning if idle, stop otherwise. Persists the resulting state to prefs. */
Tff7b72fun Td2a8fftoggleNetworkScanTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_isNetworkScanningTb4b4b4.Te6edf3valueTb4b4b4) Te6edf3stopNetworkScanTb4b4b4(Tb4b4b4) Tff7b72else Te6edf3startNetworkScanTb4b4b4(Tb4b4b4)
Te6edf3uiPrefsTb4b4b4.Te6edf3setNetworkAutoScanTb4b4b4(Te6edf3_isNetworkScanningTb4b4b4.Te6edf3valueTb4b4b4)
Tb4b4b4}

T8b949e/**
* Persist the user's intent to auto-scan the network on next screen entry without flipping the active scan flag.
* Used by the Connections screen when it must defer the actual scan start until after the system permission grant
* dialog resolves β€” the persisted intent ensures auto-start fires once permission is granted.
*/
Tff7b72fun Td2a8ffpersistNetworkAutoScanIntentTb4b4b4(Te6edf3enabledTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3uiPrefsTb4b4b4.Te6edf3setNetworkAutoScanTb4b4b4(Te6edf3enabledTb4b4b4)
Tb4b4b4}

T8b949e// ── Device selection / disconnect ───────────────────────────────────────────────────────

Tff7b72fun Td2a8ffchangeDeviceAddressTb4b4b4(Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffAttempting to change device address to Tffd700${Te6edf3addressTb4b4b4.Te6edf3anonymizeTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4}
Te6edf3radioControllerTb4b4b4.Te6edf3setDeviceAddressTb4b4b4(Te6edf3addressTb4b4b4)
Tb4b4b4}

Tff7b72fun Td2a8ffaddRecentAddressTb4b4b4(Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4, Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3addressTb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3TCP_DEVICE_PREFIXTb4b4b4)Tb4b4b4) Tff7b72return
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffaddRecentAddressTa5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3recentAddressesDataSourceTb4b4b4.Te6edf3addTb4b4b4(Te6edf3RecentAddressTb4b4b4(Te6edf3addressTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tff7b72fun Td2a8ffremoveRecentAddressTb4b4b4(Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffremoveRecentAddressTa5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3recentAddressesDataSourceTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3addressTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Called by the UI when a device has been tapped. BLE and USB entries may still need bonding/permission β€” the
* concrete return value tells the caller whether the connection was initiated immediately.
*
* @return `true` if the connection has been initiated; `false` if bonding/permission is pending.
*/
Tff7b72fun Td2a8ffonSelectedTb4b4b4(Te6edf3entryTb4b4b4: Te6edf3DeviceListEntryTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Te6edf3radioPrefsTb4b4b4.Te6edf3setDevNameTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3nameTb4b4b4)
Te6edf3addRecentAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4, Te6edf3entryTb4b4b4.Te6edf3nameTb4b4b4)
Tff7b72return Tff7b72when Tb4b4b4(Te6edf3entryTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3DeviceListEntryTb4b4b4.Te6edf3Ble Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3entryTb4b4b4.Te6edf3bondedTb4b4b4) Tb4b4b4{
Te6edf3changeDeviceAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3requestBondingTb4b4b4(Te6edf3entryTb4b4b4)
Tff7b72false
Tb4b4b4}
Tb4b4b4}

Tff7b72is Te6edf3DeviceListEntryTb4b4b4.Te6edf3Usb Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3entryTb4b4b4.Te6edf3bondedTb4b4b4) Tb4b4b4{
Te6edf3changeDeviceAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3requestPermissionTb4b4b4(Te6edf3entryTb4b4b4)
Tff7b72false
Tb4b4b4}
Tb4b4b4}

Tff7b72is Te6edf3DeviceListEntryTb4b4b4.Te6edf3Tcp Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffonSelectedTcpTa5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3changeDeviceAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4) Tb4b4b4}
Tff7b72true
Tb4b4b4}

Tff7b72is Te6edf3DeviceListEntryTb4b4b4.Te6edf3Mock Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3changeDeviceAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4)
Tff7b72true
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Initiates the bonding process and connects to the device upon success.
*
* The default implementation connects directly without explicit bonding, which is correct for Desktop/JVM where the
* OS Bluetooth stack handles pairing during the GATT connection. Android overrides this to call `createBond()`
* first.
*/
Tff7b72protected Tff7b72open Tff7b72fun Td2a8ffrequestBondingTb4b4b4(Te6edf3entryTb4b4b4: Te6edf3DeviceListEntryTb4b4b4.Te6edf3BleTb4b4b4) Tb4b4b4{
Te6edf3changeDeviceAddressTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4)
Tb4b4b4}

Tff7b72protected Tff7b72open Tff7b72fun Td2a8ffrequestPermissionTb4b4b4(Te6edf3entryTb4b4b4: Te6edf3DeviceListEntryTb4b4b4.Te6edf3UsbTb4b4b4) Tff7b72= Tffa657Unit

Tff7b72fun Td2a8ffdisconnectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3radioPrefsTb4b4b4.Te6edf3setDevNameTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3changeDeviceAddressTb4b4b4(Te6edf3NO_DEVICE_SELECTEDTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.11s